package logger

import (
	"bytes"
	"path/filepath"
	"reflect"
	"runtime"
	"time"
)

// 用interface裁剪用户代码的调用范围
type IContext interface {
	Level() Level

	//添加一个前缀
	Pre(p string) IContext

	//添加一个KV对Int64
	I(k string, v int64) IContext

	//添加一个KV对Uint64
	U(k string, v uint64) IContext

	//添加一个KV对Float64
	D(k string, v float64) IContext

	//添加一个KV对String
	S(k string, v string) IContext

	//语句消息, 只能有一个
	Msg(msg string) IContext

	//加入错误信息, 只能有一个
	Err(err error) IContext

	//结束日志拼装并输出
	Z()
}

type Context struct {
	pool   IContextPool
	logger *Logger
	level  Level

	pre []string

	fk []string
	ft []reflect.Kind

	fv_i []int64
	fv_u []uint64
	fv_d []float64
	fv_s []string

	msg string
	err error

	formatted bool
	buffer    *bytes.Buffer
	Now       time.Time

	timeFormatBuffer []byte
}

func NewContext(pool IContextPool) *Context {
	if pool == nil {
		pool = DefaultContextPool
	}
	ctx := &Context{
		pool: pool,

		pre: make([]string, 0, 4),

		fk: make([]string, 0, 32),
		ft: make([]reflect.Kind, 0, 32),

		fv_i: make([]int64, 0, 8),
		fv_u: make([]uint64, 0, 8),
		fv_d: make([]float64, 0, 8),
		fv_s: make([]string, 0, 8),

		formatted: false,
		buffer:    bytes.NewBuffer(nil),

		timeFormatBuffer: make([]byte, 64),
	}

	ctx.buffer.Grow(2048)
	return ctx
}

func (c *Context) release() {
	c.level = LevelIgnore

	c.pre = c.pre[:0]
	c.fk = c.fk[:0]
	c.ft = c.ft[:0]
	c.fv_i = c.fv_i[:0]
	c.fv_u = c.fv_u[:0]
	c.fv_d = c.fv_d[:0]
	c.fv_s = c.fv_s[:0]
	c.msg = ""
	c.err = nil

	c.buffer.Reset()
	c.formatted = false

	//release context to pool
	c.pool.Put(c)
}

func (c *Context) prepare(l *Logger, lv Level) {
	c.logger = l
	c.level = lv
}

func (c *Context) Level() Level {
	if c == nil {
		return LevelIgnore
	}

	return c.level
}

func (c *Context) Pre(p string) IContext {
	if c == nil {
		return c
	}

	c.pre = append(c.pre, p)
	return c
}

func (c *Context) I(k string, v int64) IContext {
	if c == nil {
		return c
	}

	c.fk = append(c.fk, k)
	c.ft = append(c.ft, reflect.Int64)
	c.fv_i = append(c.fv_i, v)
	return c
}

func (c *Context) U(k string, v uint64) IContext {
	if c == nil {
		return c
	}

	c.fk = append(c.fk, k)
	c.ft = append(c.ft, reflect.Uint64)
	c.fv_u = append(c.fv_u, v)
	return c
}

func (c *Context) D(k string, v float64) IContext {
	if c == nil {
		return c
	}

	c.fk = append(c.fk, k)
	c.ft = append(c.ft, reflect.Float64)
	c.fv_d = append(c.fv_d, v)
	return c
}

func (c *Context) S(k string, v string) IContext {
	if c == nil {
		return c
	}

	c.fk = append(c.fk, k)
	c.ft = append(c.ft, reflect.String)
	c.fv_s = append(c.fv_s, v)
	return c
}

func (c *Context) Msg(msg string) IContext {
	if c == nil {
		return c
	}

	c.msg = msg
	return c
}

func (c *Context) Err(err error) IContext {
	if c == nil {
		return c
	}

	c.err = err
	return c
}

func (c *Context) Z() {
	if c == nil {
		return
	}

	for _, t := range c.logger.target {
		if t.IgnoreCheck(c) {
			continue
		}
		c.format()
		t.Print(c)
	}
	c.release()
}

func (c *Context) Buffer() *bytes.Buffer {
	return c.buffer
}

func (c *Context) format() {
	if c.formatted {
		return
	}
	c.formatted = true

	f := c.logger.formatter
	f.Begin(c)

	c.Now = time.Now()
	tl := TimeFormatNowToBuffer(c.timeFormatBuffer, c.logger.Config.TimeFormat, c.Now)
	f.AppendTimeBytes(c, c.timeFormatBuffer[:tl])
	f.AppendLevel(c, c.level)

	if len(c.pre) > 0 || len(c.logger.prefix) > 0 {
		prefixCnt := 0
		f.PrefixBegin(c)
		for _, p := range c.logger.prefix {
			f.AppendPrefix(c, prefixCnt, p[0])
			prefixCnt++
		}
		for _, p := range c.pre {
			f.AppendPrefix(c, prefixCnt, p)
			prefixCnt++
		}
		f.PrefixEnd(c)
	}

	if c.logger.Config.CodeLine {
		_, file, line, ok := runtime.Caller(c.logger.Config.CodeLineSkipCaller)
		if ok {
			if !c.logger.Config.CodeLineLong {
				file = filepath.Base(file)
			}
			f.AppendCodeLine(c, file, line)
		}
	}

	if len(c.fk) > 0 {
		f.FieldBegin(c)

		i_i := 0
		i_u := 0
		i_d := 0
		i_s := 0

		for i, k := range c.fk {
			switch c.ft[i] {
			case reflect.Int64:
				f.AppendFieldInt64(c, i, k, c.fv_i[i_i])
				i_i++
			case reflect.Uint64:
				f.AppendFieldUint64(c, i, k, c.fv_u[i_u])
				i_u++
			case reflect.Float64:
				f.AppendFieldFloat64(c, i, k, c.fv_d[i_d])
				i_d++
			case reflect.String:
				f.AppendFieldString(c, i, k, c.fv_s[i_s])
				i_s++
			}
		}
		f.FieldEnd(c)
	}

	if c.msg != "" {
		f.AppendMessage(c, c.msg)
	}
	if c.err != nil {
		f.AppendError(c, c.err)
	}
	f.End(c)

	c.buffer.WriteByte('\n')
}
